classdef ziScope < EquipmentControl.ZI.ziModule.ziModule
    %ZISCOPE implements scope function of zi devices
    %   ZISCOPE module is different from other zi modules because it reads
    %   the direct data (rather than the demod data).
    
    properties
        trig_time_out       % max trigger waiting time 
        acquisition_mode    % acquisition mode: 'single' or 'continuous'
        acquisition_time    % acquisition time
        sampling_rate       % sampling rate
        frame_time          % time of a single frame
        
        mode                % measurement mode: 1 - 'time', 3 - 'frequency'
        showRuntimePlot = true;
    end
    
    properties (Access = private)
        channel  % enabled channel
        modeStr  % measurement mode: 'time' or 'frequency'
    end
    
    methods
        function obj = ziScope(dev, varargin)
            %ZISCOPE Construct an instance of ziScope module
            %   input: dev - device name, session - measurement session
            obj@EquipmentControl.ZI.ziModule.ziModule('scope', dev);
            
            p=inputParser;
            modeTest = @(x) ismember(x, {'frequency', 'time'});
            acqTest = @(x) ismember(x, {'sinlge', 'continuous'});
            
            p.addParameter('trig_time_out', inf);
            p.addParameter('acquisition_time', 60);
            p.addParameter('mode', 'time', modeTest);
            p.addParameter('acquisition', 'single', acqTest);
            p.parse(varargin{:});
            
            obj.trig_time_out = p.Results.trig_time_out;
            obj.acquisition_time = p.Results.acquisition_time;
            obj.setMode(p.Results.mode);
            
            try
                switch p.Results.acquisition
                    case 'single'
                        obj.setSingleShot();
                    case 'continuous'
                        obj.setContinuousAcquisition(p.Results.acquisition_time);
                end
            catch
                warning('Single-Shot/Continuous Acquisition Mode NOT set. [Ignore this message if using HF2]');
            end
            
            obj.init();
        end

        function obj = setMode(obj, mode)
            % set mode
            % input: mode - 'frequency' or 'time'
            p=inputParser;
            modeTest = @(x) ismember(x, {'frequency', 'time'});
            p.addRequired('mode', modeTest);
            p.parse(mode);
            
            obj.modeStr = p.Results.mode;
            switch p.Results.mode
                case 'time'
                    obj.mode = 1;
                case 'frequency'
                    obj.mode = 3;
                otherwise
                    error('Wrong mode');
            end
            obj.moduleSet('scopeModule/mode', obj.mode);
        end
        %% Channel
        function obj = enableChannel(obj, channel)
            % enable channel
            % input: '1' - channel 1 enabled, '2' - channel 2 enabled, '3' ('both') - both channels enabled
            p = inputParser;
            
            isCh = @(x) ismember(x, {'1', '2', '3', 'both'});
            p.addRequired('channel', isCh);                       
            p.parse(channel);
            
            ch = p.Results.channel;
            if strcmp(p.Results.channel, 'both')
                ch = '3';
            end
            
            obj.channel = ch;
            obj.zi.setIntCmd( ['/' obj.device '/scopes/0/channel'], str2double(ch) );
        end
        
        function obj = inputSelect(obj, channel, node)
            % select input channel and node
            % input: channel - '0' or '1', node - index of node
            p = inputParser;
            
            isCh = @(x) ismember(x, {'0', '1'});
            isNode = @(x) ismember(x, 0:227);
            p.addRequired('channel', isCh);
            p.addRequired('node', isNode); 
            p.parse(channel, node);
            
            obj.zi.setIntCmd( ['/' obj.device '/scopes/0/channels/' p.Results.channel '/inputselect'],  p.Results.node);
        end
        
        %% Acquisition
        function obj = setSingleShot(obj)
            % set single-shot mode: enable single mode, finish present
            % measurement (if exists), and unsbuscribe all nodes (if exists).
            obj.zi.setIntCmd( ['/' obj.device '/scopes/0/single'], 1);
            obj.finish();
            obj.unsubscribeNode('*');
            obj.acquisition_mode = 'single';
        end
        function obj = setContinuousAcquisition(obj, acqTime)            
            % set continuous mode: disable continuous mode, finish present
            % measurement (if exists), unsbuscribe all nodes (if exists), and set acquisition time.
            obj.zi.setIntCmd( ['/' obj.device '/scopes/0/single'], 0);
            obj.finish();
            obj.unsubscribeNode('*');            
            obj.acquisition_time = acqTime;
            obj.acquisition_mode = 'continuous';
        end
        
        function obj = setSamplingRate(obj, index)
            % set sampling rate: for MFLI rate = 60MHz/ (2^index)
            newIdx = obj.zi.getIntCmd( ['/' obj.device '/scopes/0/time']);
            tic; k = 0;
            while newIdx ~= index
                obj.zi.setIntCmd( ['/' obj.device '/scopes/0/time'], index); pause(0.01);
                newIdx = obj.zi.getIntCmd( ['/' obj.device '/scopes/0/time']);
                k = k + 1;
                if toc > 3.0
                    error('Timeout - set sampling rate failed after %d trials.', k);
                end
            end
            obj.sampling_rate = 60e6./double(2.^index);
        end
        function rate = getSamplingRate(obj)
            % get sampling rate
            index = obj.zi.getIntCmd(['/' obj.device '/scopes/0/time']);
            rate = 60e6./double(2.^index);
            obj.sampling_rate = rate;
        end

        
        function obj = setFrameLength(obj, frameTime)
            % set frame length in [second], acq points = rate*time, and max point = 5.12e6
            rate = obj.getSamplingRate();
            pts = frameTime*rate;
            if pts > 5120000
                pts = 5120000;
                warning('Too many points. Max 5120000 is set. Time = %f', pts./obj.sampling_rate);
            end
            obj.frame_time = pts./obj.sampling_rate;
            obj.zi.setDblCmd( ['/' obj.device '/scopes/0/length'], pts);
        end
        
        function obj = setAveraging(obj, num)
            % set averaging number
            % input: num - averaging number >=1
            if nargin == 1
                num = 1;
            end
            obj.moduleSet('scopeModule/averager/weight', num);
            obj.moduleSet('scopeModule/averager/restart', 0);
        end
        function obj = setFFTWindow(obj, win)
            % set FFT window
            % input: 'rect', 'hann', 'hamming', and 'BH'
            switch win
                case 'rect'
                    idx = 0;
                case 'hann'
                    idx = 1;
                case 'hamming'
                    idx = 2;
                case 'BH'
                    idx = 3;
                otherwise
                    error('NonSupported FFT Window.%s', win);
            end
                    
            obj.moduleSet('scopeModule/fft/window', idx);
        end
        
        %% Trigger
        function obj = forceTrigger(obj)
            % force trigger
            obj.zi.setIntCmd( ['/' obj.device '/scopes/0/trigforce'], 1);
        end
        function obj = setTimeOut(obj, time)
            % set trigger time out
            obj.trig_time_out = time;
        end
        function tf = isTriggerEnable(obj)
            % get trigger setting
            tf = obj.zi.getIntCmd(['/' obj.device '/scopes/0/trigenable']);
        end
        function obj = enableTrigger(obj)
            % enable trigger
            obj.zi.setIntCmd( ['/' obj.device '/scopes/0/trigenable'], 1);
        end
        function obj = disableTrigger(obj)
            % disable trigger
            obj.zi.setIntCmd( ['/' obj.device '/scopes/0/trigenable'], 0);
        end
        
        function obj = setTriggerChannel(obj, node, varargin)
            % set trigger source
            p = inputParser;
            isNode = @(x) ismember(x, 0:227);
            p.addRequired('node', isNode); 
            p.parse(node, varargin{:});            
            obj.zi.setIntCmd( ['/' obj.device '/scopes/0/trigchannel'], p.Results.node);
        end
        
        function obj = setTriggerSlope(obj, index)
            % set trigger slope: 
            % input: 1 - 'rise', 2 - 'fall', 3 - 'both'
            if ismember(index, 1:3)
                obj.zi.setIntCmd( ['/' obj.device '/scopes/0/trigslope'], index);
            else
                warning('Wrong Slope index.');
            end
        end
        function obj = setTriggerLevel(obj, level)
            % set Trigger Level
            % input: level - voltage [double]
            obj.zi.setDblCmd( ['/' obj.device '/scopes/0/triglevel'], level);
        end
        
        function obj = setTriggerHysteresis(obj, val)
            % set trigger hysteresis
            % input: val - voltage [double]
            obj.zi.setDblCmd( ['/' obj.device '/scopes/0/trighysteresis/absolute'], val);
        end
        
        function obj = enableTriggerGating(obj)
            % enable trigger gating
            obj.zi.setIntCmd( ['/' obj.device '/scopes/0/triggate/enable'], 1);
        end
        function obj = disableTriggerGating(obj)
            %disable trigger gating
            obj.zi.setIntCmd( ['/' obj.device '/scopes/0/triggate/enable'], 0);
        end
        function obj = triggerGatingSelect(obj, idx)
            % select gating index
            % input: idx = 0, 1, 2, or 3
            if ismember(idx, 0:3) % idx = 0, 1, 2, or 3
                obj.zi.setIntCmd( ['/' obj.device '/scopes/0/triggate/inputselect'], idx);
            else
                error('Wrong Trigger Gating.');
            end
        end
        function obj = setTriggerHoldoff(obj, time2)
            % set trigger hold off time
            % input: time - hold off time in [second]
            obj.zi.setDblCmd( ['/' obj.device '/scopes/0/trigholdoff'], time2);
        end
        function obj = setTriggerReference(obj, percent)
            % set trigger reference
            % input: percent - reference value * 100 
            obj.zi.setDblCmd( ['/' obj.device '/scopes/0/trigreference'], percent./100.0);
        end
        function obj = setTriggerDelay(obj, time)
            % set trigger delay 
            % input: delay - delay time in [second]
            obj.zi.setDblCmd( ['/' obj.device '/scopes/0/trigdelay'], time);
        end
        function state = getTriggerState(obj)
            % get trigger state
            state=obj.zi.getIntCmd( ['/' obj.device '/scopes/0/trigstate']);
        end
        %% Subscribe/Unsubscribe node
        function result = run(obj, varargin)
            % run the scope module to acquire data
            % option: 'processMonitor' - axis handle 
            p=inputParser;
            p.addParameter('processMonitor', @obj.plotData);
            p.parse(varargin{:});

            obj.executeModule();
            
            switch obj.acquisition_mode
                case 'single'
                    result = obj.runSingle(varargin{:});
                case 'continuous'
                    result = obj.runContinuous();
            end
        end
%%         
        function obj = subscribeChannel(obj)
            % subscribe channel(s): Scope module has at most 2 channels (MFLI)
            L=log4m.getLogger;
            wave_path = obj.make_path();
            obj.subscribeNode(wave_path);
            L.info('ziScope::subscribeChannel', sprintf('Node %s subscribed', wave_path));
            obj.moduleSet('scopeModule/mode', obj.mode);
        end
        
        function obj = unsubscribeChannel(obj)
            % unsubscribe channel(s): Scope module has at most 2 channels (MFLI)
            L=log4m.getLogger;
            wave_path = obj.subscribed_paths{1}.path_string;
            obj.unsubscribeNode(wave_path);
            L.info('ziScope::unsubscribeChannel', sprintf('Node %s unsubscribed', wave_path) );
            obj.disableScope();
        end
        function obj = reset(obj)
            % reset module: call reset function of ziModule, and disableScope
            reset@EquipmentControl.ZI.ziModule.ziModule(obj);            
            obj.disableScope();
        end
        
        function obj = enableScope(obj)
            % enable scope, wait until the scope is enabled.
            ch = '0';  tic;
            isEnbled = obj.zi.getIntCmd( ['/' obj.device '/scopes/' ch '/enable']);
            while isEnbled == 0
                obj.zi.setIntCmd( ['/' obj.device '/scopes/0/enable'], 1); 
                pause(0.01);                                                              % it's better to put a pause between 'set' and 'get'
                isEnbled = obj.zi.getIntCmd( ['/' obj.device '/scopes/' ch '/enable']);
                if toc > 10.0
                    error('Timeout - enable scope failed.');
                end
            end
        end
        
        function obj = disableScope(obj)
            % disable scope, wait until the scope is disabled.
            ch = '0';  tic;
            isEnbled = obj.zi.getIntCmd( ['/' obj.device '/scopes/' ch '/enable']);
            while isEnbled == 1
                obj.zi.setIntCmd( ['/' obj.device '/scopes/0/enable'], 0); 
                pause(0.01);
                isEnbled = obj.zi.getIntCmd( ['/' obj.device '/scopes/' ch '/enable']);
                if toc > 10.0
                    error('Timeout - disable scope failed.');
                end
            end
        end
    end

    methods
        %% monitor 
        function plotData(obj , ~, data)
            % default monitor function
            switch obj.modeStr
                case 'time'
                    x_axis = data.time;
                    xlim(obj.monitor_hdl, [min(data.time) max(data.time)]);
                case 'frequency'
                    x_axis = data.freq;
                    xlim(obj.monitor_hdl, [min(data.freq) max(data.freq)]);
            end
            
            switch obj.channel
                case {'1', '2'}
                    obj.curves(1).XData = x_axis;
                    obj.curves(1).YData = data.value;
                    obj.curves(2).XData = [];
                    obj.curves(2).YData = [];
                    legend(['wave' obj.channel]);
                case '3'
                    obj.curves(1).XData = x_axis;
                    obj.curves(1).YData = data.value(:, 1);
                    obj.curves(2).XData = x_axis;
                    obj.curves(2).YData = data.value(:, 2);
                    legend({'wave1', 'wave2'});
            end
            drawnow limitrate    
        end
        
        function monitor_plot_init(obj)
            % monitor initialization
            color_array = obj.monitor_hdl.ColorOrder;
            obj.curves = [line(obj.monitor_hdl, 'Color', color_array(1,:), 'DisplayName', 'wave1'), ...
                          line(obj.monitor_hdl, 'Color', color_array(2,:), 'DisplayName', 'wave2') ];
            legend('show');
            grid on;
            switch obj.modeStr
                case 'time'
                    obj.monitor_hdl.XScale = 'linear';
                    obj.monitor_hdl.YScale = 'linear';
                case 'frequency'
                    obj.monitor_hdl.XScale = 'log';
                    obj.monitor_hdl.YScale = 'log';
            end
        end   
        function obj = disableRuntimePlot(obj)
            obj.showRuntimePlot = false;
        end
        function obj = enableRuntimePlot(obj)
            obj.showRuntimePlot = true;
        end

    end
    
    %% private
    methods (Access = private)
        function wave_path = make_path(obj)
            channelIndex = '0';
            path = EquipmentControl.ZI.ziData.ziPath(obj.device, 'scopes', channelIndex, 'wave', '');
            wave_path = path.path_string();
            obj.subscribed_paths = {path};
            obj.curve_info=cellfun(@(p) p.channel_sample_str, obj.subscribed_paths);
        end
        
        function result = runSingle(obj, varargin)
            clear textprogressbar
            L = log4m.getLogger;
            
            p=inputParser;
            p.addParameter('processMonitor', @obj.plotData);
            p.parse(varargin{:});
            if obj.showRuntimePlot
                processMonitor = p.Results.processMonitor;  obj.monitor_plot_init();
            end
            obj.enableScope();
            
            isTimeOut = false;
            if obj.isTriggerEnable
                textprogressbar( sprintf('%s Waiting for trigger ... (timeout = %f s)\n', datestr(now,'yyyy-mm-dd HH:MM:SS,FFF'), obj.trig_time_out) );
                isTriggered = false; pause(0.5); tic; % wait 0.5 s to get rid of initial trig signal, i.e., deadtime = 0.5s
                while obj.progressModule < 1  && ~isTimeOut 
                    pause(0.1); 
                    if ~obj.getTriggerState() && ~isTriggered
                        if toc > obj.trig_time_out
                            L.info('ziScope::runSingle', 'Trigger timeout' );
                            isTimeOut = true;
                        end
                    end
                        
                    if obj.getTriggerState() && ~isTriggered
                        isTriggered = true; tic;
                        L.info('ziScope::runSingle', 'Trigger detected' );
                    end
                    
                    if isTriggered
                        textprogressbar( min(100.0, toc/obj.frame_time*100) );
                    end
                end
                
                
            else % trigger disable - free run
                textprogressbar( sprintf('%s Acquiring Data,\t', datestr(now,'yyyy-mm-dd HH:MM:SS,FFF')) ); tic;            
                while obj.progressModule < 1 
                    pause(0.1); 
                    textprogressbar( min(100.0, toc/obj.frame_time*100) );
                end
            end            
            obj.disableScope();
            
            if ~isTimeOut
                textprogressbar(100); textprogressbar('.\t done.');
                tmp = obj.read();
                obj.finish();
                result = EquipmentControl.ZI.ziData.ziScopeData(tmp,'domain',obj.modeStr);
                obj.measured_data = result;
                if obj.showRuntimePlot
                    processMonitor(obj.monitor_hdl, result.getNodeData() );
                end
            else
                result = [];
            end
        end
        
        function result = runContinuous(obj, varargin)
            clear textprogressbar
            L = log4m.getLogger;
            
            p=inputParser;
            p.addParameter('processMonitor', @obj.plotData);
            p.parse(varargin{:});
            if obj.showRuntimePlot
                processMonitor = p.Results.processMonitor;  obj.monitor_plot_init();
            end

            obj.enableScope();
            
            isTriggered = false; isTimeOut = false; tic;
            if obj.isTriggerEnable
                textprogressbar( sprintf('%s Waiting for trigger ... (timeout = %f s)\n', datestr(now,'yyyy-mm-dd HH:MM:SS,FFF'), obj.trig_time_out) );
            else
                textprogressbar( sprintf('%s Acquiring Data,\t', datestr(now,'yyyy-mm-dd HH:MM:SS,FFF')) );
            end
            
            while ~obj.finishedModule
                pause(0.1);
                tmp = obj.read();
                run_time = toc;
                if ziCheckPathInData(tmp, obj.subscribed_paths{1}.path_channel_string)
                    if obj.isTriggerEnable && ~isTriggered
                        L.info('ziScope::runContinuous', 'Trigger detected' );
                    end
                    if obj.showRuntimePlot
                        data = EquipmentControl.ZI.ziData.ziScopeData(tmp,'domain',obj.modeStr);
                        processMonitor(obj.monitor_hdl, data.getNodeData() );
                    end
                    isTriggered = true;
                    
                    if toc <  obj.acquisition_time
                        textprogressbar(toc/obj.acquisition_time *100);
                    else
                        textprogressbar(100);
                        obj.disableScope();
                        obj.finish();
                    end
                else
                    if run_time > obj.trig_time_out && ~isTriggered
                        obj.disableScope();
                        obj.finish();
                        L.info('ziScope::runContinuous', 'Trigger timeout');
                        isTimeOut = true;
                    end
                end
            end
            
            obj.disableScope();
            
            if ~isTimeOut
                textprogressbar(100); textprogressbar('.\t done.');
                obj.finish();
                result = EquipmentControl.ZI.ziData.ziScopeData(tmp,'domain',obj.modeStr);
                obj.measured_data = result;
                if obj.showRuntimePlot
                    processMonitor(obj.monitor_hdl, result.getNodeData() );
                end
            else
                result = [];
            end
        end
        
        function init(obj)
            % get present settings of zi machine
            rate_index = obj.zi.getIntCmd(['/' obj.device '/scopes/0/time']);
            pts = obj.zi.getDblCmd(['/' obj.device '/scopes/0/length']);            
            obj.sampling_rate = 60e6./double(2.^rate_index);
            obj.frame_time = pts./obj.sampling_rate;
        end
    end
end

